#include <fhepython/PyEnv.h>
#include <fhepython/PyNode.h>
#include <fhepython/PyCall.h>
#include <fhe/math/Math.h>
#include <fhe/core/Util.h>
#include <boost/preprocessor/seq/for_each.hpp>

namespace fhe
{
  using namespace core;
  namespace python
  {
    template <typename T>
    boost::python::object defineVec2()
    {
      using namespace fhe::core;
      boost::python::scope c = 
        boost::python::class_<Vec<2,T> >( Vec<2,T>::typeName().c_str(),
                                          boost::python::init<>() )
          .def( boost::python::init<T,T>() )
          .def( boost::python::init<Rot<2,T>,T >() )
          .def_readwrite( "x", &Vec<2,T>::x )
          .def_readwrite( "y", &Vec<2,T>::y )
          .def( "__repr__", &Vec<2,T>::toString )
          .def( "__eq__", &Vec<2,T>::operator== )
          .def( boost::python::self + boost::python::other<Vec<2,T> >() )
          .def( boost::python::self - boost::python::other<Vec<2,T> >() )
          .def( boost::python::self * boost::python::other<Vec<2,T> >() )
          .def( boost::python::self / boost::python::other<Vec<2,T> >() )
          .def( boost::python::self * boost::python::other<Rot<2,T> >() )
          .def( boost::python::self * T() )
          .def( boost::python::self / T() )
          .def( "squaredLength", &Vec<2,T>::squaredLength )
          .def( "length", &Vec<2,T>::length )
          .def( "toRot", &Vec<2,T>::toRot )
          .def( "normalize", &Vec<2,T>::normalize )
          .def( "dot", &Vec<2,T>::dot )
          .def( "project", &Vec<2,T>::project )
          .def( "cross", &Vec<2,T>::cross )
          .def( "perp", &Vec<2,T>::perp )
          .def( "distToLine", &Vec<2,T>::distToLine )
        ;
      c.attr( "ZERO" ) = Vec<2,T>::ZERO;
      c.attr( "ONE" ) = Vec<2,T>::ONE;
      return c;
    }
    
    template <typename T>
    boost::python::object defineVec3()
    {
      using namespace fhe::core;
      boost::python::scope c =
        boost::python::class_<Vec<3,T> >( Vec<3,T>::typeName().c_str(),
                                          boost::python::init<>() )
          .def( boost::python::init<T,T,T>() )
          .def_readwrite( "x", &Vec<3,T>::x )
          .def_readwrite( "y", &Vec<3,T>::y )
          .def_readwrite( "z", &Vec<3,T>::z )
          .def( "__repr__", &Vec<3,T>::toString )
          .def( "__eq__", &Vec<3,T>::operator== )
          .def( boost::python::self + boost::python::other<Vec<3,T> >() )
          .def( boost::python::self - boost::python::other<Vec<3,T> >() )
          .def( boost::python::self * boost::python::other<Vec<3,T> >() )
          .def( boost::python::self / boost::python::other<Vec<3,T> >() )
          .def( boost::python::self * boost::python::other<Vec<3,T> >() )
          .def( boost::python::self * boost::python::other<Rot<3,T> >() )
          .def( boost::python::self * T() )
          .def( boost::python::self / T() )
          .def( "squaredLength", &Vec<3,T>::squaredLength )
          .def( "length", &Vec<3,T>::length )
          .def( "normalize", &Vec<3,T>::normalize )
          .def( "dot", &Vec<3,T>::dot )
          .def( "project", &Vec<3,T>::project )
          .def( "cross", &Vec<3,T>::cross )
        ;
      c.attr( "ZERO" ) = Vec<3,T>::ZERO;
      c.attr( "ONE" ) = Vec<3,T>::ONE;
      return c;
    }
    
    template <typename T>
    boost::python::object defineRot2()
    {
      using namespace fhe::core;
      boost::python::scope c =
        boost::python::class_<Rot<2,T> >( Rot<2,T>::typeName().c_str(),
                                          boost::python::init<>() )
          .def( "fromDegrees", &Rot<2,T>::fromDegrees )
          .staticmethod( "fromDegrees" )
          .def( "fromRadians", &Rot<2,T>::fromRadians )
          .staticmethod( "fromRadians" )
          .def( "__repr__", &Rot<2,T>::toString )
          .def( "__eq__", &Rot<2,T>::operator== )
          .def( "radians", &Rot<2,T>::radians )
          .def( "degrees", &Rot<2,T>::degrees )
          .def( "inverse", &Rot<2,T>::inverse )
          .def( boost::python::self + boost::python::other<Rot<2,T> >() )
          .def( boost::python::self - boost::python::other<Rot<2,T> >() )
        ;
      c.attr( "IDENTITY" ) = Rot<2,T>::IDENTITY;
      return c;
    }
    
    template <typename T>
    boost::python::tuple rot3ToAxisAngle( Rot<3,T> r )
    {
      Vec<3,T> axis;
      T angle;
      r.toAxisAngle( axis, angle );
      return boost::python::make_tuple( axis, angle );
    }
    
    template <typename T>
    boost::python::object defineRot3()
    {
      using namespace fhe::core;
      boost::python::scope c = 
        boost::python::class_<Rot<3,T> >( Rot<3,T>::typeName().c_str(),
                                          boost::python::init<>() )
          .def( boost::python::init<T,T,T,T>() )
          .def( boost::python::init<Vec<3,T>,T>() )
          .def( "__repr__", &Rot<3,T>::toString )
          .def( "__eq__", &Rot<3,T>::operator== )
          .def( boost::python::self * boost::python::other<Rot<3,T> >() )
          .def( boost::python::self * boost::python::other<Vec<3,T> >() )
          .def( "toAxisAngle", &rot3ToAxisAngle<T> )
          .def( "normalize", &Rot<3,T>::normalize )
          .def( "inverse", &Rot<3,T>::inverse )
        ;
      c.attr( "IDENTITY" ) = Rot<3,T>::IDENTITY;
      return c;
    }
    
    template <typename T>
    boost::python::object defineMat2()
    {
      using namespace fhe::core;
      boost::python::scope c =
        boost::python::class_<Mat<2,T> >( Mat<2,T>::typeName().c_str(),
                                          boost::python::init<>() )
          .def( "__repr__", &Mat<2,T>::toString )
          .def( "__eq__", &Mat<2,T>::operator== )
          .def( boost::python::self * boost::python::other<Mat<2,T> >() )
          .def( boost::python::self * boost::python::other<Vec<2,T> >() )
          .def( "inverse", &Mat<2,T>::inverse )
          .def( "translation", &Mat<2,T>::translation )
          .staticmethod( "translation" )
          .def( "rotation", &Mat<2,T>::rotation )
          .staticmethod( "rotation" )
          .def( "scale", &Mat<2,T>::scale )
          .staticmethod( "scale" )
          .def( "getTranslation", &Mat<2,T>::getTranslation )
          .def( "getRotation", &Mat<2,T>::getRotation )
          .def( "getScale", &Mat<2,T>::getScale )
        ;
      c.attr( "IDENTITY" ) = Mat<2,T>::IDENTITY;
      c.attr( "ZERO" ) = Mat<2,T>::ZERO;
      return c;
    }

    template <typename T>
    boost::python::object defineMat3()
    {
      using namespace fhe::core;
      boost::python::scope c =
        boost::python::class_<Mat<3,T> >( Mat<3,T>::typeName().c_str(),
                                          boost::python::init<>() )
          .def( "__repr__", &Mat<3,T>::toString )
          .def( "__eq__", &Mat<3,T>::operator== )
          .def( boost::python::self * boost::python::other<Mat<3,T> >() )
          .def( boost::python::self * boost::python::other<Vec<3,T> >() )
          .def( "inverse", &Mat<3,T>::inverse )
          .def( "translation", &Mat<3,T>::translation )
          .staticmethod( "translation" )
          .def( "rotation", &Mat<3,T>::rotation )
          .staticmethod( "rotation" )
          .def( "scale", &Mat<3,T>::scale )
          .staticmethod( "scale" )
          .def( "getTranslation", &Mat<3,T>::getTranslation )
          .def( "getRotation", &Mat<3,T>::getRotation )
          .def( "getScale", &Mat<3,T>::getScale )
        ;
      c.attr( "IDENTITY" ) = Mat<3,T>::IDENTITY;
      c.attr( "ZERO" ) = Mat<3,T>::ZERO;
      return c;
    }

    PyEnv::PyEnv()
    {
      try
      {
        Py_Initialize();
        
        mainModule_ = boost::python::import( "__main__" );
        mainNamespace_ = boost::python::dict( mainModule_.attr( "__dict__" ) );
        builtins_ = mainNamespace_[ "__builtins__" ].attr( "__dict__" );
        
        mainNamespace_[ "Node" ] = PyNode::define();
        mainNamespace_[ "Call" ] = PyCall::define();
        mainNamespace_[ "node" ] = boost::python::make_function( &PyNode::reg );
        mainNamespace_[ Vec2i::typeName() ] = defineVec2<int>();
        mainNamespace_[ Vec2d::typeName() ] = defineVec2<double>();
        mainNamespace_[ Vec3i::typeName() ] = defineVec3<int>();
        mainNamespace_[ Vec3d::typeName() ] = defineVec3<double>();
        mainNamespace_[ Rot2i::typeName() ] = defineRot2<int>();
        mainNamespace_[ Rot2d::typeName() ] = defineRot2<double>();
        mainNamespace_[ Rot3i::typeName() ] = defineRot3<int>();
        mainNamespace_[ Rot3d::typeName() ] = defineRot3<double>();
        mainNamespace_[ Mat2i::typeName() ] = defineMat2<int>();
        mainNamespace_[ Mat2d::typeName() ] = defineMat2<double>();
        mainNamespace_[ Mat3i::typeName() ] = defineMat3<int>();
        mainNamespace_[ Mat3d::typeName() ] = defineMat3<double>();
      }
      catch ( boost::python::error_already_set )
      {
        PyErr_Print();
        PyErr_Clear();
        FHE_ERROR( "failed to initialize python environment" );
      }
    }
    
    PyEnv::~PyEnv()
    {
      Py_Finalize();
    }
    
    PyEnv& PyEnv::instance()
    {
      static PyEnv pyEnv;
      return pyEnv;
    }
    
    boost::python::dict PyEnv::defaultNamespace()
    {
      boost::python::dict ns;
      ns.update( mainNamespace_ );
      return ns;
    }
    
    void PyEnv::runFile( const std::string& filename, boost::python::dict ns )
    {
      try
      {
        boost::python::exec_file( filename.c_str(), ns, ns );
      }
      catch ( boost::python::error_already_set )
      {
        PyErr_Print();
        PyErr_Clear();
        FHE_ERROR( "error execing file %s", filename.c_str() );
      }
    }
    
    void PyEnv::exec( const std::string& script, boost::python::dict ns )
    {
      try
      {
        boost::python::exec( script.c_str(), ns, ns );
      }
      catch ( boost::python::error_already_set )
      {
        PyErr_Print();
        PyErr_Clear();
        FHE_ERROR( "error running script %s", script.c_str() );
      }
    }
    
    boost::python::object PyEnv::eval( const std::string& script, boost::python::dict ns )
    {
      try
      {
        return boost::python::eval( script.c_str(), ns, ns );
      }
      catch ( boost::python::error_already_set )
      {
        PyErr_Print();
        PyErr_Clear();
        FHE_ERROR( "error evaling script %s", script.c_str() );
        return boost::python::object();
      }
    }
    
    std::string PyEnv::getType( boost::python::object obj )
    {
      return boost::python::extract<std::string>( builtins_[ "type" ]( obj ).attr( "__name__" ) );
    }
    
    std::string PyEnv::toString( boost::python::object obj )
    {
      return boost::python::extract<std::string>( builtins_[ "repr" ]( obj ) );
    }
    
    bool PyEnv::callable( boost::python::object obj )
    {
      return boost::python::extract<bool>( builtins_[ "callable" ]( obj ) );
    }
    
    boost::python::object PyEnv::convertVal( const Val& val )
    {
      if ( val.empty() )
      {
        return boost::python::object();
      }
      else if ( val.is<bool>() )
      {
        return boost::python::object( val.get<bool>() );
      }
      else if ( val.is<int>() )
      {
        return boost::python::object( val.get<int>() );
      }
      else if ( val.is<float>() )
      {
        return boost::python::object( val.get<float>() );
      }
      else if ( val.is<double>() )
      {
        return boost::python::object( float( val.get<double>() ) );
      }
      else if ( val.is<std::string>() )
      {
        return boost::python::object( val.get<std::string>() );
      }
      else
      {
        FHE_ERROR( "unable to convert %s to python", val.type().c_str() );
        throw;
      }
    }
    
    Val PyEnv::convertVal( boost::python::object obj )
    {
      std::string type = getType( obj );
      if ( type == "bool" )
      {
        return Val( bool( boost::python::extract<bool>( obj ) ) );
      }
      else if ( type == "int" )
      {
        return Val( int( boost::python::extract<int>( obj ) ) );
      }
      else if ( type == "float" )
      {
        return Val( float( boost::python::extract<float>( obj ) ) );
      }
      else if ( type == "str" )
      {
        return Val( std::string( boost::python::extract<std::string>( obj ) ) );
      }
      else if ( type == "NoneType" )
      {
        return Val();
      }
      else
      {
        FHE_ERROR( "unable to convert %s from python", type.c_str() );
      }
      return Val();
    }
  }
}

BOOST_PYTHON_MODULE( fhepython )
{
  fhe::python::PyNode::define();
  def( "node", &fhe::python::PyNode::reg );
  fhe::python::PyCall::define();
  fhe::python::defineVec2<int>();
  fhe::python::defineVec2<double>();
  fhe::python::defineVec3<int>();
  fhe::python::defineVec3<double>();
  fhe::python::defineRot2<int>();
  fhe::python::defineRot2<double>();
  fhe::python::defineRot3<int>();
  fhe::python::defineRot3<double>();
  fhe::python::defineMat2<int>();
  fhe::python::defineMat2<double>();
  fhe::python::defineMat3<int>();
  fhe::python::defineMat3<double>();
}
